const env = {
  str: null,
  pos: 0,
};

const parse = function parse(str) {
  env.str = str;
  env.pos = 0;
  const result = doParse();
  if (env.pos !== str.length) throw new Error('parse failed');
  return result;
};

const doParse = function doParse() {
  for(let parseFunc of parseAll) {
    const val = parseFunc();
    if (val !== undefined) {
      return val;
    }
  }
};

const parseTrue = function parseTrue() {
  const flag = env.str.substr(env.pos, 4) === 'true';
  if (flag) {
    env.pos += 4;
    return true;
  }
};
const parseFalse = function parseFalse() {
  const flag = env.str.substr(env.pos, 5) === 'false';
  if (flag) {
    env.pos += 5;
    return false;
  }
};
const parseNull = function parseNull() {
  const flag = env.str.substr(env.pos, 4) === 'null';
  if (flag) {
    env.pos += 4;
    return null;
  }
};
const parseNumber = function parseNumber() {
  let str = '';
  let pos = env.pos;
  const get = function get() {
    const char = env.str.charAt(pos);
    let val = null;
    if (char === '-' || char === '.' || char === 'e' || char === 'E' || (char >= '0' && char <= '9')) {
      pos += 1;
      return char;
    };
  };
  let next;
  while(next = get()) {
    str += next;
  }
  if (str.length !== 0) {
    if (isNaN(str)) throw new Error('bad number');
    env.pos += str.length;
    return parseFloat(str, 10);
  }
};
const parseString = function parseString() {
  let str = '';
  let pos = env.pos;
  if (env.str.charAt(pos) !== '"') return;
  pos += 1;
  const get = function get() {
    const char = env.str.charAt(pos);
    let val = null;
    if (char === '"') return;
    if (char === '\\') {
      pos += 2;
      return escape(env.str.substr(pos - 2, 2));
    }
    pos += 1;
    return char;
  };
  let next;
  while(env.str.charAt(pos) !== '"' && (next = get())) {
    str += next;
  }
  if (env.str.charAt(pos) === '"') {
    env.pos = pos + 1;
    return str;
  }
  throw new Error('bad string');
};
const escape = function escape(str) {
  console.log(str);
  switch(str) {
    case '\\\\': return '\\';
    case '\\b': return '\b';
    case '\\f': return '\f';
    case '\\n': return '\n';
    case '\\r': return '\r';
    case '\\t': return '\t';
    case '\\"': return '"';
    default: return str;
  }
};
const parseArray = function parseArray() {
  const arr = [];
  let pos = env.pos;
  if (env.str.charAt(pos) !== '[') return;
  env.pos += 1;
  white();
  let next;
  while(env.str.charAt(env.pos) !== ']' && (next = doParse())) {
    arr.push(next);
    if (env.str.charAt(env.pos) === ',') env.pos += 1;
    white();
  }
  if (env.str.charAt(env.pos) === ']') {
    env.pos += 1;
    return arr;
  }
  throw new Error('bad array');
};
const parseObject = function parseObject() {
  const obj = {};
  let pos = env.pos;
  if (env.str.charAt(pos) !== '{') return;
  env.pos += 1;
  white();
  let key;
  while(env.str.charAt(env.pos) !== '}' && (key = parseString())) {
    if (env.str.charAt(env.pos) !== ':') throw new Error('bad object');
    env.pos += 1;
    white();
    const val = doParse();
    if (!val) throw new Error('bad object');
    obj[key] = val;
    if (env.str.charAt(env.pos) === ',') env.pos += 1;
    white();
  }
  if (env.str.charAt(env.pos) === '}') {
    env.pos += 1;
    return obj;
  }
  throw new Error('bad object');
};
const white = function white() {
  let char = env.str.charAt(env.pos);
  while(char === ' ' || char === '\n') {
    env.pos += 1;
    char = env.str.charAt(env.pos)
  }
};

const parseAll = [
  parseObject,
  parseArray,
  parseString,
  parseNumber,
  parseTrue,
  parseFalse,
  parseNull,
];

export default {
  parse,
};
